/*
 * Copyright (c) 2012 Johan Fylling
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

part of engine;


class CollidableCircle extends Collidable {
  num radius;
  
  CollidableCircle (num this.radius);
  
  /** See [Collidable.boundingSphereRadius]. */
  num get boundingSphereRadius => this.radius;
  
  /** See [Collidable.getFoci]. */
  void getFoci (List<Vector2> foci, DynamicBody body) {
    foci.addLast(new Vector2.copy(body.position));
  }
  
  /** See [Collidable.getAxes]. */
  void getAxes (List<Vector2> axes, List<Vector2> foci, DynamicBody body) {
    // Circles don't have axes.
  }
  
  /** See [Collidable.project]. */
  Interval project (Vector2 axis, DynamicBody body) {
    num min = 0.0;
    num max = 0.0;
    
    if (this.radius == 0.0) {
      min = max = axis.dot(body.position);
    } else {
      Vector2 radiusAxis = axis * this.radius;
      min = axis.dot(body.position - radiusAxis);
      num val = axis.dot(body.position + radiusAxis);
      if (val >= min) {
        max = val;
      } else {
        max = min;
        min = val;
      }
    }
    
    return new Interval(min, max);
  }
  
  /** See [Collidable.getFarthestFeature]. */
  ContactFeature getFarthestFeature (DynamicBody body, Vector2 direction) {
    return new ContactVertex(body.position + direction.normalized()*this.radius);
  }
  
  /** See [Collidable.raycast]. */
  bool raycast (Vector2 from, Vector2 to, DynamicBody body) {
    Vector2 dir = to - from;
    Vector2 diff = from - body.position;
    num a = dir.dot(dir);
    num b = diff.dot(dir);
    num c = diff.dot(diff) - this.radius * this.radius;
    
    num disc = b * b - a * c;
    if (disc < 0.0 || a < 1e-15) {
      return false;
    }
    
    num sqrtDisc = Math.sqrt(disc);
    num invA = 1.0 / a;
    num t = -(b + sqrtDisc);
    if (0.0 <= t && t <= a) {
      to.clone(from + dir*t*invA);
      return true;
    }
    return false;
  }
  
  /** See [Collidable.getRandomPoint]. */
  Vector2 getRandomPoint (DynamicBody body, Math.Random random) {
    num length = random.nextDouble()*this.radius;
    Vector2 orientation = new Vector2.fromAngle(random.nextDouble()*Math.PI*2);
    orientation.normalize();
    return body.position + orientation*length;
  }
  
  toJson () {
    var data = super.toJson();
    assert(data is Map);
    data["type"] = "collidable_circle";
    data["radius"] = radius;
    return data;
  }
}


class CollidableCircleStorageHandler {
  call (StorageManager manager, Map attributes) {
    var radius = attributes["radius"];
    assert(radius is num);
    
    return new CollidableCircle(radius);
  }
}
